package pers.liyan.shi.springboot.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
import pers.liyan.shi.springboot.bean.Employee;
import pers.liyan.shi.springboot.mapper.EmployeeMapper;

@Service
/**
 * @CacheConfig抽取公共配置
 */
@CacheConfig(cacheNames = {"emp"}, cacheManager = "empCacheManager")
public class EmployeeService {

    @Autowired
    private EmployeeMapper mapper;

    /**
     * @Cacheable将方法结果进行缓存, 再次请求相同的数据时, 从缓存中获取
     *
     * 几个属性
     *  cacheNames/value: 指定将结果放在哪些缓存中, 数组, CacheManager管理多个Cache组件, 每个缓存组件都有唯一的名称, 此属性指定使用的缓存组件名称
     *  key: 缓存数据时使用的key,默认使用方法的参数 可以使用SpEl表达式
     *      #id:参数id的值  等价于#a0 #p0 #root.args[0]
     *      key = "#root.methodName + '[' + #id + ']'" --> getEmpById[1]
     *  keyGenerator: key生成器, 可以使用指定的key生成器, 与key属性二选一
     *      可以使用自定义的KayGenerator(keyGenerator = "myKeyGenerator"), {@link pers.liyan.shi.springboot.config.MyCacheConfig}
     *  cacheManager: 指定使用的缓存管理器;或者指定缓存解析器cacheResolver
     *  condition: 指定符合条件的情况才缓存, SpEL
     *      condition = "#a0 > 1" --> 第一个参数>1才进行缓存
     *  unless: 指定不进行缓存的条件, 可以获取到结果进行判断
     *      unless = "#result == null" --> 结果为空不缓存
     *  sync: 是否异步
     *          默认使用同步的方式, 异步模式下不支持unless
     *
     *  原理:
     *      1. 自动配置类
     *      2. 缓存配置类
     *      org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
     *      org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
     *      org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
     *      org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
     *      org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
     *      org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
     *      org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
     *      org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
     *      org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
     *      org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
     *      org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
     *      3. 默认生效的缓存配置 SimpleCacheConfiguration
     *      4. 在容器中添加了一个 ConcurrentMapCacheManager
     *      5. 可以创建和获取ConcurrentMapCache, 数据保存在ConcurrentMap中
     *
     * @Cacheable
     *  运行流程:
     *      1. 方法运行之前， 先查询Cache， 按照cacheName指定的名字获取
     *          CacheManager获取缓存， 缓存为null则创建缓存
     *      2. Cache组件根据key查询缓存， key按照策略生成， 默认使用SimpleKeyGenerator生成
     *          生成策略:
     *              方法没有参数, 使用 SimpleKey.EMPTY
     *              有一个参数, 使用参数值作为key
     *              有多个参数, key = new SimpleKey(params)
     *      3. 如果没有查询到缓存， 调用目标方法
     *      4. 将目标方法的返回值加入缓存
     *
     *  @Cacheable 标注的方法执行之前现在缓存中查询数据, 默认使用参数作为key, 如果没有数据, 调用方法, 将结果缓存
     *  核心:
     *      1). 使用CacheManager获取Cache组件
     *      2). 根据KeyGenerator生成的key获取缓存中的数据, 查询到缓存直接返回, 不调用方法
     *      3). 否则调用方法并将结果加入缓存
     *
     * @param id
     * @return
     */
    @Cacheable(/*cacheNames = "emp", condition = "#a0 > 1"*/unless = "#result == null ")
    public Employee getEmpById(Integer id){
        System.out.println("查询员工: " + id);
        return mapper.getEmpById(id);
    }

    /**
     * @CachePut: 调用方法更新缓存
     * 常用于修改了数据库的数据, 同时更新缓存
     * 先调用目标方法, 再将目标方法返回值进行缓存
     *
     * 注意:
     *  更新和查询是一定要使用相同的key,默认使用的都是参数值作为缓存, key不一致无法更新对应的缓存
     *  key = "#employee.id"
     *  key = "#result.id"(不要在@Cacheable使用#result, 因为@Cacheable标注的方法运行之前就要使用key查询缓存)
     * @param employee
     * @return
     */
    @CachePut(/*cacheNames = "emp", */key = "#result.id")
    public Employee updateEmp(Employee employee){
        System.out.println("更新员工信息:" + employee);
        mapper.updateEmp(employee);
        return employee;
    }

    //@CachePut(cacheNames = "emp", key = "#employee.id")
    public Employee insertEmp(Employee employee){

        System.out.println("新增员工:" + employee);
        mapper.insertEmp(employee);
        return employee;

    }


    /**
     * @CacheEvict 缓存清除
     *  key 指定要删除数据的key
     *  allEntries 是否清除当前缓存的全部数据
     *  beforeInvocation 是否在方法执行之前清除缓存(默认在方法执行之后)
     *      默认false:
     *          如果方法在执行过程中出现异常, 缓存不会被清除
     *      true:
     *          无论方法是否出现异常都会删除缓存
     * @param id
     */
    @CacheEvict(/*cacheNames = "emp", */key = "#id")
    public void deleteEmp(Integer id){

        System.out.println("删除员工:" + id);
        int i = 10/0;
        mapper.deleteEmpById(id);

    }


    /**
     *  注condition判断在key生成之前, unless判断在key生成之后, 所以key使用#result时判断#result是否为null要在condition进行判断
     *  否则可能导致NPE
     *
     * @CachePut会导致方法一定会执行
     * @param lastName
     * @return
     */
    @Caching(
            cacheable = {
                    @Cacheable(/*cacheNames = {"emp", "employee"}, */key = "#lastName", condition = "#result != null")
            },
            put = {
                    @CachePut(/*cacheNames = "emp", */key = "#result.id", condition = "#result != null"),
                    @CachePut(/*cacheNames = "emp", */key = "#result.email", condition = "#result != null")
            }
    )
    public Employee getEmpByLastName(String lastName) {
        return mapper.getEmpByLastName(lastName);
    }


}
